package client.auction;

/**
 * This class represents a client of an auction-service.
 * A client sends commands to a server like !login, !bid, !create, !logout, !list ...
 * The client and the server communicate over a tcp-socket. The client also gets
 * udp-datagram notifications from the server, handled in an own thread.
 *
 * @version 1.0 vom 13.10.2012
 * @author Peter Tillian 1026312
 */

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;

import security.SecureHelper;
import security.channel.Channel;
import security.channel.TCPChannel;

import org.bouncycastle.util.encoders.Base64;

public class AuctionClient {
	public static void main(String[] args) {
		checkArguments(args);
		BufferedReader inReader = new BufferedReader(new InputStreamReader(
				System.in));
		Channel tcpChannel = null;
		Channel aesChannel = null;
		Channel channel = null;
		Socket socket = null;
		String host = args[0];
		int tcpPort = Integer.parseInt(args[1]);
		int udpPort = Integer.parseInt(args[2]); // not necessary for lab3
		String keyServerPub = args[3];
		String keyDirClient = args[4];
		Key secretKey = null;

		try {
			socket = new Socket(host, tcpPort);
		} catch (UnknownHostException e1) {
			System.err.println("Don't know about host " + args[0]);
			System.exit(1);
		} catch (IOException e2) {
			System.err.println("Couldn't get I/O for the connection to host: "
					+ args[0] + ": " + e2.getMessage());
			System.exit(1);
		}

		try {
			tcpChannel = new TCPChannel(socket);
			channel = tcpChannel;
		} catch (IOException e) {
			System.err.println("Couldn't create channels: " + e.getMessage());
		}

		String userInput = "";
		try {
			while ((userInput = inReader.readLine()) != null) {
				String checkedInput = checkUserInput(userInput);
				if (checkedInput.equals("ok")) {
					if (userInput.startsWith("!login")) {
						if (aesChannel != null) {
							System.out.println("You are already logged in.");
							continue;
						} else {
							// make handshake with server and get aesChannel
							aesChannel = SecureHelper.createAESChannelClient(
									keyServerPub, keyDirClient, userInput,
									tcpPort, socket);
							if (aesChannel == null) {
								System.out.println("Couldn't log in!");
							} else {
								if (secretKey == null) {
									secretKey = SecureHelper
											.createSharedSecretKey(keyDirClient
													+ userInput.split(" ")[1]
															.toLowerCase()
													+ ".key");
								}

								channel = aesChannel;
							}
						}
					} else if (userInput.equals("!logout")) {
						if (aesChannel == null) {
							System.out.println("You have to log in first!");
						} else {
							channel.println(userInput);
							System.out.println(channel.readLine());
							aesChannel = null;
							channel = tcpChannel;
						}
					} else if (userInput.equals("!end")) {
						channel.println(userInput);
						System.out.println("Client shut down successfully!");
						break;
					} else if (userInput.startsWith("!list")) {
						channel.println(userInput);
						String serverReceive = channel.readLine();

						// HMAC
						// verifying==========================================
						if (aesChannel != null) {
							String auctionList = serverReceive.split("HMAC:")[0];
							byte[] receivedHMAC = Base64.decode(serverReceive
									.split("HMAC:")[1].getBytes());
							try {
								byte[] generatedHMAC = SecureHelper
										.createHashMAC(secretKey, auctionList);
								boolean validHash = MessageDigest.isEqual(
										generatedHMAC, receivedHMAC);
								if (!validHash) { // retry if failed first time
									System.out.println(auctionList.replace(
											"={()?!", "\n"));
									// send request again to server
									channel.println(userInput);
									serverReceive = channel.readLine();
									auctionList = serverReceive.split("HMAC:")[0];
									receivedHMAC = Base64.decode(serverReceive
											.split("HMAC:")[1].getBytes());
									try {
										generatedHMAC = SecureHelper
												.createHashMAC(secretKey,
														auctionList);
										validHash = MessageDigest.isEqual(
												generatedHMAC, receivedHMAC);
										// print again and dont retry if agian
										// not
										// valid
										System.out.println(auctionList.replace(
												"={()?!", "\n"));
									} catch (InvalidKeyException e) {
										System.out
												.println("ERROR: Invalid shared secret key.");
									}
								} else {
									System.out.println(auctionList.replace(
											"={()?!", "\n"));
								}
							} catch (InvalidKeyException e) {
								System.out
										.println("ERROR: Invalid shared secret key.");
							}
						} else {
							System.out.println(serverReceive.replace("={()?!",
									"\n"));
						}
					} else if (userInput.startsWith("!bid")
							|| userInput.startsWith("!create")
							|| userInput.startsWith("!groupBid")
							|| userInput.startsWith("!confirm")) {
						if (aesChannel == null) {
							System.out.println("You have to log in first!");
							continue;
						}

						// send to and receive from server
						channel.println(userInput);
						if (userInput.startsWith("!confirm")) {
							System.out
									.println("YOU ARE BLOCKED UNTIL ENOUGH CONFIRMS OR SERVER rejects YOU.");
						}
						System.out.println(channel.readLine());
					}
				} else {
					System.out.println(checkedInput);
				}
			}
		} catch (IOException e) {
			if (channel.isInputShutdown()) {
				System.err
						.println("Error reading line from stdin or writing to socket: "
								+ e.getMessage());
			} else {
				System.err.println("Server shut down!");
			}

		}

		// free ressources
		try {
			channel.close();
			inReader.close();
		} catch (IOException e) {
			System.err.println("Error free ressourcing: " + e.getMessage());
		}
	} // end of main

	/**
	 * This method checks the input arguments. It must be 5 arguments, and the
	 * second and the third must be numeric.
	 */
	private static void checkArguments(String[] args) {
		if (args.length != 5) {
			System.err
					.println("Usage: client <host> <tcpPort> <udpPort> <server.key.pub> <clients.key.dir>");
			System.exit(1);
		}
		try {
			Integer.parseInt(args[1]);
			Integer.parseInt(args[2]);
		} catch (NumberFormatException e) {
			System.err
					.println("Usage: client <host> <tcpPort> <udpPort> <server.key.pub> <clients.key.dir>");
			System.err.println("<tcpPort> and <udpPort> must be numeric");
			System.exit(1);
		}
	}

	/**
	 * This method checks the user-inputs. !login, !bid, !logout, !end, !create,
	 * !list are i.e. correct user-inputs. The keyword and the arguments of the
	 * keyword are beeing checked.
	 * 
	 * @return Returns "ok" if input was valid, or an error-description for the
	 *         specific command.
	 */
	public static String checkUserInput(String input) {
		if (input.startsWith("!login")) {
			if (input.split(" ").length == 2)
				return "ok";
			else
				return "Usage: !login <name>";
		} else if (input.startsWith("!logout")) {
			if (input.equals("!logout"))
				return "ok";
			else
				return "Usage: !logout";
		} else if (input.startsWith("!end")) {
			if (input.equals("!end"))
				return "ok";
			else
				return "Usage: !end";
		} else if (input.startsWith("!create")) {
			if (input.split(" ").length >= 3) {
				try {
					Integer.parseInt(input.split(" ")[1]);
				} catch (NumberFormatException e) {
					return "Usage: !create <duration> <description>      <duration> must be integer-numeric";
				}
				return "ok";
			} else
				return "Usage: !create <duration> <description>";
		} else if (input.startsWith("!list")) {
			if (input.equals("!list"))
				return "ok";
			else
				return "Usage: !list";
		} else if (input.startsWith("!bid")) {
			if (input.split(" ").length == 3) {
				try {
					Integer.parseInt(input.split(" ")[1]);
					Double.parseDouble(input.split(" ")[2]);
				} catch (NumberFormatException e) {
					return "Usage: !bid <auction-id> <amount>      <auction-id> must be integer-numeric, <amount> must be integer or double-numeric";
				}
				return "ok";
			} else
				return "Usage: !bid <auction-id> <amount>";
		} else if (input.startsWith("!groupBid")) {
			if (input.split(" ").length == 3) {
				try {
					Integer.parseInt(input.split(" ")[1]);
					Double.parseDouble(input.split(" ")[2]);
				} catch (NumberFormatException e) {
					return "Usage: !groupBid <auction-id> <amount>      <auction-id> must be integer-numeric, <amount> must be integer or double-numeric";
				}
				return "ok";
			} else {
				return "Usage: !groupBid <auction-id> <amount>      <auction-id> must be integer-numeric, <amount> must be integer or double-numeric";
			}
		} else if (input.startsWith("!confirm")) {
			if (input.split(" ").length == 4) {
				try {
					Integer.parseInt(input.split(" ")[1]);
					Double.parseDouble(input.split(" ")[2]);
				} catch (NumberFormatException e) {
					return "Usage: !confirm <auction-id> <amount> <username>      <auction-id> must be integer-numeric, <amount> must be integer or double-numeric";
				}
				return "ok";
			} else {
				return "Usage: !confirm <auction-id> <amount> <username>      <auction-id> must be integer-numeric, <amount> must be integer or double-numeric";
			}
		}
		return "Valid commands: !list, !login, !logout, !create, !bid, !end";
	}
} // end of class Client
